Dart 健全的空安全: Beta 版
作者 / Michael Thomsen, Dart & Flutter Product Manager, Google
我们为 Dart 和 Flutter 带来了健全的空安全 Beta 版。空安全是提升生产力的最新功能,可帮助您避免空值错误,这类错误通常很难发现。想快速了解我们对空安全如此期待的原因,请观看这支视频:
健全的空安全
https://dart.cn/null-safety
腾讯视频链接
https://v.qq.com/x/page/n3208kis0wj.html
Bilibili 视频链接
https://www.bilibili.com/video/BV1GV411Y7sW/
随着空安全 Beta 版的发布,是时候开始动员社区对 pub.dev 上的数千个 package 进行迁移了。我们已经迁移了 Dart 核心库、Flutter 框架以及超过 40 个 Dart 和 Flutter package。同时我们希望社区也能开始迁移 package,拥抱空安全。
pub.dev https://pub.flutter-io.cn/ 空安全 package https://pub.flutter-io.cn/packages?q=&prerelease-null-safe=1
在 Beta 版的基础上,我们也开始向空安全的稳定版发起冲刺。希望您能够开始使用空安全,并告诉我们任何可以改进之处,包括界面消息是否易于理解,或者文档是否清晰易读。我们非常期待您的反馈。
提交反馈 https://github.com/dart-lang/sdk/issues/new?title=Null%20safety%20feedback:%20[issue%20summary]&labels=NNBD&body=Describe%20the%20issue%20or%20potential%20improvement%20in%20detail%20here
启用空安全
在讨论空安全迁移之前,我们需要重申 (如我们的空安全原则中所述),何时开始采用空安全完全由您来决定。如果应用和 package 的最低 Dart SDK 版本约束为 Dart 2.12 预发布版或更高版本,它们将默认以空安全状态运行:
空安全原则 https://dart.cn/null-safety#null-safety-principles 启用空安全 https://dart.cn/null-safety#enable-null-safety
environment:
sdk: ">=2.12.0-0 <3.0.0"
如需体验空安全,请尝试创建 (例如,使用 dart create) 一个包含如下代码的小型空安全 hello 应用。然后,您可以尝试在更改 SDK 约束并运行 dart pub get 前后分别运行该应用,来体验程序行为的变化。(请确保使用 dart --version 命令,返回的是 2.12 的 SDK 版本)
bin/hello.dart:
...
void main() {
var hello = 'Hello Dart developers';
if (someCondition) {
hello = null;
}
print(hello);
}
Before changing the SDK constraint:
$ dart run
null
After changing the SDK constraint (and running dart pub get):
$ dart run
bin/hello.dart:6:13: Error: Null can't be assigned to a variable of type 'String' because 'String' is not nullable.
hello = null;
^
迁移至空安全
要将 package (或简单应用) 迁移至空安全,请按照以下五个步骤进行操作,详细的内容说明请查看迁移指南:
第 1 步: 检查以确保依赖项已就绪
我们强烈建议按顺序迁移代码,首先迁移依赖关系图中的子项。如下图所示,如果 C 依赖于 B,而 B 依赖于 A,那么应首先将 A 迁移到空安全,然后再依次迁移 B 和 C。无论 A、B 和 C 是库、package 还是应用,该顺序都适用。
迁移顺序为何如此重要?虽然您可以在迁移依赖项之前先处理一些代码迁移工作,但如果依赖项在迁移期间更改了其 API,那么您将面临必须重新执行迁移的风险。如果您的某些依赖项没有支持空安全,请考虑使用 pub.dev 上 package 所给出的联系方式与 package 发布者联系。
验证依赖项是否已就绪
要验证您的应用或 package 是否已准备好开始迁移,您可以在空安全模式下使用 dart pub outdated。比如下图所示应用,只需将其依赖项 path、process 和 pedantic 升级到 Resolvable 列中给出的预发布版本,就表示已做好了迁移的准备。
如果依赖项的小版本升级已经提供了空安全支持,则会显示在 Upgradable 列中。空安全支持通常由大版本更新来提供,这时 outdated 输出结果的 Resolvable 下方将列出相应的版本。要升级到这些版本,请编辑 pubspec.yaml 文件以允许获取这些大版本更新。例如,将 process: ^3.0.13 更改为 process: ^4.0.0-nullsafety。
collection 1.15 https://pub.flutter-io.cn/packages/collection/versions/1.15.0-nullsafety.5 空安全搜索选项 https://pub.flutter-io.cn/packages?q=&prerelease-null-safe=1
第 2 步: 使用迁移工具进行迁移
依赖项准备就绪后,您就可以使用迁移工具 dart migration 来迁移应用或 package 了。
迁移工具是交互式的,您可以查看工具推断出的可空属性。如果您对工具给出的结论有异议,则可以添加可空性提示以更改推断。适当添加迁移提示有可能大幅提升迁移质量。
我们曾安排少量的 Dart package 作者使用空安全的早期预览版进行了测试性迁移,并取得了颇为积极的结果。迁移指南中给出了更多迁移工具的使用技巧。
迁移指南 https://dart.cn/null-safety/migration-guide
在 IDE 或命令行中使用 pub get 更新 package。然后使用 IDE 或命令行对您的 Dart 代码执行静态分析:
$ dart pub get
$ dart analyze
如果是 Flutter 代码,执行静态分析的命令如下:
$ flutter pub get
$ flutter analyze
第 5 步: 发布您的空安全 package
迁移完成且测试通过后,您便可以将 package 发布为预发布版。这里简单给出两种最佳做法供参考:
将您的版本号升至下一个大版本 (例如,从 2.3.x 到 3.0.0)。此最佳做法可确保您的 package 用户不会在尚未做好使用空安全的准备时就升级至该版本,并且使您可以自由重构 API 以最充分地利用空安全。 将 package 在 pub.dev 上以预发行的方式进行发布。(例如,使用 3.0.0-nullsafety.0,而非 3.0.0)
以预发行的方式发布 https://dart.cn/tools/pub/publishing#publishing-prereleases
https://dart.cn/null-safety/migration-guide
健全空安全的优势
在之前 Dart 和 Flutter 空安全支持的技术预览版中,我们结合大量示例探讨了空安全的优势。随着空安全的逐步完成,我们将分享给大家一些真实案例中看到的空安全的优势。
就在最近,我们在 Flutter 的 master 渠道中发现了一个错误,多个 flutter 工具命令在特定计算机配置下会因为空值错误发生崩溃: The method '>=' was called on null。根本问题源于近期的一项拉取请求 (PR),用于添加对 Android Studio 4.1 的检测。该 PR 添加了如下代码:
final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
/// plugin path of Android Studio changed after version 4.1.
if (major >= 4 && minor >= 1) {
…
您能发现里面的问题吗?因为 version 可能为空,major 和 minor 也都可能为空。如果单独检查这段代码,这个错误似乎并不难发现。但实际上,即使经过了严格的代码审查 (如 Flutter repo 所用代码审查流程),也总是难免有这样的漏网之鱼。借助空安全,静态分析能够立即捕捉到这一问题:
Dartpad 捕捉此问题的演示 https://dartpad.cn/0e9797be7488d8ec6c3fca92b7f2740f
这只是一个非常简单的错误。在 Google 内部早期使用空安全代码时,捕捉并解决的复杂错误远不止于此。例如:
一个内部团队发现,他们经常在代码中检查是否存在空值,但空安全知道那些值永远不会为空。这一状况常见于使用 protobuf 的代码,在这些代码中,可选字段在未经设置时会返回默认值,而永远不会为空。这会导致代码混淆默认值和空值,并错误地检查默认条件。
Google Pay 团队在他们的 Flutter 代码中发现了一些错误,在尝试访问 Widget 上下文之外的 Flutter State 对象时会出错。在引入空安全之前,这些代码会返回空值并掩盖错误;通过空安全健全的分析,确定这些属性永远不会为空,并抛出分析错误。
Flutter 团队发现了一个错误,如果向 Window.render() 中的 scene 参数传递空值,则 Flutter 引擎可能会崩溃。在进行空安全迁移期间,他们添加了一个提示以将 Scene 标记为不可空,然后便可轻松防止传递空值可能会引发的应用崩溃问题。
Protocol Buffers https://developers.google.cn/protocol-buffers 将 Scene 标记为不可空 https://github.com/cbracken/engine/blob/bad869e229a8a02cad6e63d12e80807b33b5c12f/lib/ui/window.dart#L1069
hello_world 示例应用 https://github.com/flutter/flutter/blob/master/examples/hello_world/lib/main.dart 应用大小比较 https://gist.github.com/mit-mit/64e160f9dc3bf6c69c7ef2f81384594a
代码运行速度方面,如果必须强制执行健全类型系统,则可能会增加开销。但是,由于空值检查变少,因此也可能会提高代码运行的速度。我们初步的基准分析表明,性能与以前的版本处于同等水平,并且新的附加类型信息为我们将来实现新的性能提升带来了可能性。我们计划在未来更详细地和大家分享在性能提升方面所做的工作。
在某些情况下,我们已经看到空安全可以提高性能,特别是在向空安全迁移的过程中发现代码逻辑中的缺陷。例如,我们在 Flutter web 的文本布局缓存中发现了一个问题。此缓存使用了一个可空的键,然后某些逻辑会在出现空值时使用 TextAlign.start。这种逻辑在缓存中造成了一个缺陷: 元素虽保有默认值,但看起来却像发生了变化。其结果是频繁出现缓存未命中的情况。添加不可空的 textAlign getter 有助于修复缓存缺陷,在文本得到缓存的情况下,其渲染性能提高了 14 倍。
即刻开始体验!
Dart SDK 归档
https://dart.cn/tools/sdk/archive#beta-channel
迁移指南
https://dart.cn/null-safety/migration-guide
提供反馈
https://github.com/dart-lang/sdk/issues/new?title=Null%20safety%20feedback:%20[issue%20summary]&labels=NNBD&body=Describe%20the%20issue%20or%20potential%20improvement%20in%20detail%20here
推荐阅读